package com.nts.visionworks.business.contents.controller;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.nts.visionworks.business.contents.domain.Contents;
import com.nts.visionworks.business.contents.domain.ContentsAttribute;
import com.nts.visionworks.business.contents.domain.ContentsElement;
import com.nts.visionworks.business.contents.domain.ContentsMedia;
import com.nts.visionworks.business.contents.domain.ContentsMediaThumbnail;
import com.nts.visionworks.business.contents.domain.ContentsTemplate;
import com.nts.visionworks.business.contents.domain.ContentsTemplateAttribute;
import com.nts.visionworks.business.contents.domain.ContentsTemplateAttributeId;
import com.nts.visionworks.business.contents.domain.ContentsTemplateElement;
import com.nts.visionworks.business.contents.domain.UnitElement;
import com.nts.visionworks.business.contents.exception.InvalidParameterException;
import com.nts.visionworks.business.contents.service.ContentsService;
import com.nts.visionworks.core.ffmpeg.FFMpegFrameExtractor;
import com.nts.visionworks.core.hibernate.query.PaginationDirector;
import com.nts.visionworks.core.image.scaling.ImageScaler;
import com.nts.visionworks.core.mediainfo.MediaInfo;
import com.nts.visionworks.core.mediainfo.MediaInfoWrapper;
import com.nts.visionworks.core.mediainfo.StreamType;
import com.nts.visionworks.core.mediainfo.SupportContentsType;
import com.nts.visionworks.core.mtn.MtnFrameExtractor;
import com.nts.visionworks.core.multipart.MultipleFileSupportMultipartResolver;
import com.nts.visionworks.core.multipart.domain.Archive;
import com.nts.visionworks.core.multipart.service.MultipartService;
import com.nts.visionworks.core.utility.GuidUtil;
import com.nts.visionworks.core.utility.Pair;

/**
 * 컨텐츠 관련 요청을 처리하기 위한 인터페이스를 제공한다.
 * @author Devcken
 */
@Controller
@RequestMapping("/contents")
public class ContentsController
{
	private final static Logger logger = LoggerFactory.getLogger(ContentsController.class);
	
	/**
	 * 끝점 관련 DAO를 주입받는다.
	 */
	@Resource(name="contentsService")
	private ContentsService _contentsService;
	
	/**
	 * 멀티파트 관련 DAO를 주입받는다.
	 */
	@Resource(name="multipartService")
	private MultipartService _multipartService;
	
	/**
	 * FFMpeg을 이용한 Frame Extractor를 주입받는다.
	 */
	@Resource(name="ffmpegFrameExtractor")
	private FFMpegFrameExtractor _ffmpegFrameExtractor;
	
	/**
	 * Movie Thumbnailer를 이용한 Frame Extractor를 주입받는다.
	 */
	@Resource(name="mtnFrameExtractor")
	private MtnFrameExtractor _mtnFrameExtractor;
	
	/**
	 * MediaInfo를 사용하기 위한 Wrapper를 주입받는다.
	 */
	@Resource(name="mediaInfoAccess")
	private MediaInfoWrapper _mediaInfoWrapper;
	
	/**
	 * 데이터 페이지네이션을 위한 지시자를 주입받는다.
	 */
	@Resource(name="paginationDirector")
	private PaginationDirector _paginationDirector;
	
	/**
	 * 단위 원소 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @return 조회된 단위 원소 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/unitelement/list", method=RequestMethod.GET)
	public ModelAndView listUnitElements(HttpServletRequest request)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<UnitElement> unitElements = this._contentsService.listUnitElements();
			
			map.put("unitElements", unitElements);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 단위 원소 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param unitElement 저장하고자 하는 단위 원소 정보가 전달된다.
	 * @return 저장된 단위 원소 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/unitelement/save", method=RequestMethod.GET)
	public ModelAndView saveUnitElement(HttpServletRequest request,
			UnitElement unitElement)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			this._contentsService.saveUnitElement(unitElement);
			
			map.put("unitElement", unitElement);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 템플릿 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @return 조회된 컨텐츠 템플릿 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/template/list", method=RequestMethod.GET)
	public ModelAndView listContentsTemplates(HttpServletRequest request)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<ContentsTemplate> templates = this._contentsService.listContentsTemplates();
			
			map.put("templates", templates);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 템플릿 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param template 저장하려는 컨텐츠 템플릿 정보가 전달된다.
	 * @param attributesCount 컨텐츠 템플릿 속성의 개수가 전달된다.
	 * @param elementCount 컨텐츠 템플릿 요소의 개수가 전달된다.
	 * @return 저장된 컨텐츠 템플릿 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/template/save", method=RequestMethod.GET)
	public ModelAndView saveContentsElement(HttpServletRequest request,
			ContentsTemplate template,
			@RequestParam(value="attributesCount", required=true)Integer attributesCount,
			@RequestParam(value="elementsCount", required=true)Integer elementsCount)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			for (int index = 0; index < attributesCount; index++)
			{
				String attributeKey = request.getParameter(String.format("attribute_key_%04d", index));
				String attributeName = request.getParameter(String.format("attribute_name_%04d", index));
				String essentiality = request.getParameter(String.format("attribute_essentiality_%04d", index));
				String inputType = request.getParameter(String.format("attribute_input_type_%04d", index));
				String additionalComposition = request.getParameter(String.format("attribute_additional_composition_%04d", index));
				String defaultValue = request.getParameter(String.format("attribute_default_value_%04d", index));
				String description = request.getParameter(String.format("attribute_description_%04d", index));
				
				if (attributeKey == null)
				{
					throw new Exception("");
				}
				
				if (attributeName == null)
				{
					throw new Exception("");
				}
				
				if (essentiality == null)
				{
					throw new Exception("");
				}
				
				if (inputType == null)
				{
					throw new Exception("");
				}
				
				ContentsTemplateAttribute attribute = new ContentsTemplateAttribute();
				
				attribute.setAttributeKey(attributeKey);
				attribute.setAttributeName(attributeName);
				attribute.setEssentialityYn(Boolean.parseBoolean(essentiality));
				attribute.setInputType(Short.parseShort(inputType));
				attribute.setAdditionalComposition(additionalComposition);
				attribute.setDefaultValue(defaultValue);
				attribute.setDescription(description);
				
				template.getContentsTemplateAttributes().add(attribute);
			}
			
			for (int index = 0; index < elementsCount; index++)
			{
				String headName = request.getParameter(String.format("element_headname_%04d", index));
				String elementOrder = request.getParameter(String.format("element_orders_%04d", index));
				String mediaLimit = request.getParameter(String.format("element_media_limit_%04d", index));
				
				if (headName == null)
				{
					throw new Exception("");
				}
				
				if (elementOrder == null)
				{
					throw new Exception("");
				}
				
				if (mediaLimit == null)
				{
					throw new Exception("");
				}
				
				ContentsTemplateElement element = new ContentsTemplateElement();
				
				element.setHeadName(headName);
				element.setElementOrder(Integer.parseInt(elementOrder));
				element.setMediaLimit(Integer.parseInt(mediaLimit));
				
				template.getContentsTemplateElements().add(element);
			}
			
			this._contentsService.saveContentsTemplate(template);
			
			map.put("template", template);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 템플릿 삭제 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param templateId 삭제하고자 하는 컨텐츠 템플릿 아이디가 전달된다.
	 * @return 삭제된 컨텐츠 템플릿 아이디를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/template/delete", method=RequestMethod.GET)
	public ModelAndView deleteContentsTemplate(HttpServletRequest request,
			@RequestParam(value="templateId", required=true)Integer templateId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			this._contentsService.deleteContentsTemplate(templateId);
			
			map.put("templateId", templateId);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		
		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param searchCondition 컨텐츠 목록 검색 조건 정보가 전달된다.
	 * @param templateId 컨텐츠를 구성하는 컨텐츠 템플릿 아이디가 검색을 목적으로 전달된다.
	 * @return 컨텐츠 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/list", method =RequestMethod.GET)
	public ModelAndView listContents(HttpServletRequest request,
			Contents searchCondition,
			@RequestParam(value="templateId", required=false)Integer templateId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<Contents> contents = this._contentsService.listContents(searchCondition, templateId.intValue());

			map.put("contents", contents);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}

	/**
	 * 컨텐츠 정보 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contents 저장할 컨텐츠 정보가 전달된다.
	 * @param attributesCount 컨텐츠의 속성 정보 개수가 전달된다.
	 * @param elementsCount 컨텐츠의 단위 요소 정보 개수가 전달된다.
	 * @return 저장된 컨텐츠 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	public ModelAndView saveContents(
			HttpServletRequest request,
			Contents contents,
			@RequestParam(value="attributesCount", required=true)Integer attributesCount,
			@RequestParam(value="elementsCount", required=true)Integer elementsCount)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<ContentsAttribute> attributes = new ArrayList<ContentsAttribute>(attributesCount.intValue());
			List<ContentsElement> elements = new ArrayList<ContentsElement>(elementsCount.intValue());

			// 파라메터로 전달된 컨텐츠 속성 정보 개수만큼 속성 정보 파라메터들을 Request로부터 가져온다.
			for (int index = 0; index < attributesCount; index++)
			{
				String attributeKeyParameterId = String.format("attribute_key_%04d", index);
				String attributeValueParameterId = String.format("attribute_value_%04d", index);
				String templateAttributeIdParemeterId = String.format("attribute_template_attribute_id_%04d", index);

				String attributeKey = request.getParameter(attributeKeyParameterId);
				String attributeValue = request.getParameter(attributeValueParameterId);
				String templateAttributeId = request.getParameter(templateAttributeIdParemeterId);

				if (attributeKey == null)
				{
					throw new InvalidParameterException("attributeKey", attributeKeyParameterId, attributeKey);
				}

				if (attributeValue == null)
				{
					throw new InvalidParameterException("attributeValue", attributeValueParameterId, attributeValue);
				}

				if (templateAttributeId == null)
				{
					throw new InvalidParameterException("templateAttributeId", templateAttributeIdParemeterId, templateAttributeId);
				}

				ContentsAttribute attribute = new ContentsAttribute();

				ContentsTemplateAttribute templateAttribute = new ContentsTemplateAttribute();

				templateAttribute.setId(new ContentsTemplateAttributeId(Integer.parseInt(templateAttributeId), contents.getContentsTemplate().getTemplateId()));

				attribute.setAttributeKey(attributeKey);
				attribute.setAttributeValue(attributeValue);
				attribute.setContentsTemplateAttribute(templateAttribute);

				attributes.add(attribute);
			}

			// 파라메터로 전달된 컨텐츠 단위 요소 정보 개수만큼 단위 요소 정보 파라메터들을 Request로부터 가져온다.
			for (int index = 0; index < elementsCount; index++)
			{
				String templateElementIdParameterId = String.format("element_template_element_id_%04d", index);
				String elementOrdersParameterId = String.format("element_orders_%04d", index);

				String templateElementId = request.getParameter(templateElementIdParameterId);
				String elementOrder = request.getParameter(elementOrdersParameterId);

				if (templateElementId == null)
				{
					throw new InvalidParameterException("templateElementId", templateElementIdParameterId, templateElementId);
				}

				if (elementOrder == null)
				{
					throw new InvalidParameterException("elementOrder", elementOrdersParameterId, elementOrder);
				}

				ContentsElement element = new ContentsElement();

				ContentsTemplateElement templateElement = new ContentsTemplateElement();

				templateElement.setTemplateElementId(Integer.valueOf(Integer.parseInt(templateElementId)));

				element.setContentsTemplateElement(templateElement);
				element.setElementOrder(Integer.parseInt(elementOrder));

				elements.add(element);
			}

			contents.getContentsAttributes().addAll(attributes);
			contents.getContentsElements().addAll(elements);

			this._contentsService.saveContents(contents);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}

	/**
	 * 컨텐츠 삭제 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contentsId 삭제한 컨텐츠의 아이디가 전달된다.
	 * @return 컨텐츠 삭제 여부가 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/delete", method=RequestMethod.GET)
	public ModelAndView deleteContents(HttpServletRequest request,
			@RequestParam(value="contentsId", required=true)Long contentsId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			boolean result = this._contentsService.deleteContents(contentsId.longValue());

			map.put("result", Boolean.valueOf(result));
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 단위 요소 목록 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contentsId 단위 요소에 대한 컨텐츠 아이디가 전달된다.
	 * @return 조회된 컨텐츠 단위 요소 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/element/list", method=RequestMethod.GET)
	public ModelAndView listContentsElements(HttpServletRequest request,
			@RequestParam(value="contentsId", required=true)Long contentsId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<ContentsElement> contentsElements = this._contentsService.listContentsElements(contentsId);

			map.put("contentsElements", contentsElements);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 단위 요소 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contentsId 컨텐츠 단위 요소에 대한 컨텐츠 아이디가 전달된다.
	 * @param templateElementId 컨텐츠 단위 요소에 대한 컨텐츠 템플릿 단위 요소 아이디가 전달된다.
	 * @param contentsElement 저장하려는 컨텐츠 단위 요소 정보가 전달된다.
	 * @return 저장된 컨텐츠 단위 요소 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/element/save", method=RequestMethod.GET)
	public ModelAndView saveContentsElement(HttpServletRequest request,
			@RequestParam(value="contentsId", required=true)Long contentsId,
			@RequestParam(value="templateElementId", required=true)Integer templateElementId,
			ContentsElement contentsElement)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			boolean result = this._contentsService.saveContentsElement(contentsId, templateElementId, contentsElement);

			map.put("result", result);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 속성 목록 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contentsId 컨텐츠 속성에 대한 컨텐츠 아이디가 전달된다.
	 * @return 조회된 컨텐츠 속성 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/attribute/list", method=RequestMethod.GET)
	public ModelAndView listContentsAttributes(HttpServletRequest request,
			@RequestParam(value="contentsId", required=true)Long contentsId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<ContentsAttribute> contentsAttributes = this._contentsService.listContentsAttributes(contentsId);

			map.put("contentsAttributes", contentsAttributes);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 속성 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param templateId 컨텐츠 속성에 대한 컨텐츠 템플릿 아이디가 전달된다.
	 * @param templateAttributeId 컨텐츠 속성에 대한 컨텐츠 템플릿 속성 아이디가 전달된다.
	 * @param contentsId 컨텐츠 속성에 대한 컨텐츠 아이디가 전달된다.
	 * @param contentsAttribute 저장하려는 컨텐츠 속성 정보가 전달된다.
	 * @return 저장된 컨텐츠 속성 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/attribute/save", method=RequestMethod.GET)
	public ModelAndView saveContentsAttribute(HttpServletRequest request,
			@RequestParam(value="templateId", required=true)Integer templateId,
			@RequestParam(value="templateAttributeId", required=true)Integer templateAttributeId,
			@RequestParam(value="contentsId", required=true)Long contentsId,
			ContentsAttribute contentsAttribute)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			boolean result = this._contentsService.saveContentsAttribute(templateId, templateAttributeId, contentsId, contentsAttribute);

			map.put("result", result);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}

	/**
	 * 컨텐츠 미디어 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @return 컨텐츠 미디어 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/media/list", method=RequestMethod.GET)
	public ModelAndView listContentsMedia(HttpServletRequest request)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<Pair<ContentsMedia, Archive>> contentsMedia = this._contentsService.listContentsMedia();

			map.put("contentsMedia", contentsMedia);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 미디어 저장 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param contentsMedia 저장하려는 컨텐츠 미디어 정보가 전달된다.
	 * @return 저장된 컨텐츠 미디어 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/media/save", method=RequestMethod.GET)
	public ModelAndView saveContentsMedia(HttpServletRequest request,
			ContentsMedia contentsMedia)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			// 컨텐츠 미디어의 Archive 정보를 가져온다.
			Archive archive = this._multipartService.getArchive(contentsMedia.getArchiveId());
			
			// Archive 정보 중 ContentsType의 파일 종류 부분을 추출한다.
			String fileType = archive.getContentType().substring(0, archive.getContentType().indexOf("/"));
			
			// 파일 종류에 따라 파일의 세부 정보를 추출한다.
			switch (fileType)
			{
				case "video":
					
					
					
					break;
					
				case "image":
					
					
					
					break;
			}
			
			//_mediaInfoWrapper.get(StreamType., streamNumber, parameterIndex)
			
			this._contentsService.saveContentsMedia(contentsMedia);

			map.put("contentsMedia", contentsMedia);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 미디어의 섬네일 목록 조회 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param mediaId 요청하려는 섬네일 목록의 컨텐츠 미디어 아이디가 전달된다.
	 * @return 컨텐츠 미디어 섬네일 목록을 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/media/thumbnails/list", method=RequestMethod.GET)
	public ModelAndView listContentsMediaThumbnails(HttpServletRequest request,
			@RequestParam(value="mediaId", required=true)Long mediaId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			List<ContentsMediaThumbnail> contentsMediaThumbnails = this._contentsService.listContentsMediaThumbnails(mediaId);
			
			map.put("contentsMediaThumbnail", contentsMediaThumbnails);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 컨텐츠 미디어의 섬네일 생성 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param session HTTP 세션에 대한 정보가 전달된다.
	 * @param mediaId 섬네일을 생성하려는 컨텐츠 미디어의 아이디가 전달된다.
	 * @param thumbnailWidth 생성하려는 섬네일의 최대 너비가 전달된다.
	 * @param thumbnailHeight 생성하려는 섬네일의 최대 높이가 전달된다.(컨텐츠 미디어 파일이 이미지인 경우에만 사용된다.)
	 * @param thumbnailTimeSecond 컨텐츠 미디어 파일이 동영상인 경우, 섬네일을 생성하려는 시간이 초단위로 전달된다. 
	 * @return 컨텐츠 미디어 섬네일 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/media/thumbnail/generate", method=RequestMethod.GET)
	public ModelAndView generateContentsMediaThumbnail(HttpServletRequest request,
			HttpSession session,
			@RequestParam(value="mediaId", required=true)Long mediaId,
			@RequestParam(value="thumbnailWidth", required=true)Integer thumbnailWidth,
			@RequestParam(value="thumbnailHeight", required=false)Integer thumbnailHeight,
			@RequestParam(value="thumbnailTime", required=false)Integer thumbnailTimeSecond)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			// 컨텐츠 미디어 정보를 조회한다.
			ContentsMedia contentsMedia = this._contentsService.getContentsMedia(mediaId);
			
			// 컨텐츠 미디어에 대한 아카이브 정보를 조회한다.
			Archive contentsMediaArchive = this._multipartService.getArchive(contentsMedia.getArchiveId());
			
			// 컨텐츠 미디어의 컨텐츠 유형을 가져온다.
			SupportContentsType contentsType = SupportContentsType.cast(contentsMediaArchive.getContentType().split("/")[0]);
			
			// 아카이브 정보로부터 파일의 실제 경로를 조합한다.
			String mediaRealPath = String.format("%s%s%s",
					MultipleFileSupportMultipartResolver.getUploadRootDirectory(),
					contentsMediaArchive.getRelativePath(),
					contentsMediaArchive.getMaskedName());
			
			MediaInfo mediaInfo = this.getMediaInfo(contentsMediaArchive);
			
			// 섬네일의 마스크 이름을 설정한다.
			String thumbnailMaskedName = String.format("%s.%s",
					GuidUtil.generateGuid(),
					FFMpegFrameExtractor._FRAME_IMAGE_EXTENSION);
			
			// 섬네일의 실제 경로를 조합한다.
			String thumbnailRealPath = String.format("%s%s%s",
					MultipleFileSupportMultipartResolver.getUploadRootDirectory(),
					contentsMediaArchive.getRelativePath(),
					thumbnailMaskedName);
			
			boolean success = false;
			
			switch (contentsType)
			{
				// 컨텐츠 미디어 파일이 이미지인 경우, 스케일링한다.
				case IMAGE:
					
					success = ImageScaler.scaleImage(mediaRealPath, thumbnailRealPath, thumbnailWidth, thumbnailHeight);
					
					break;
					
				// 컨텐츠 미디어 파일이 비디오인 경우, 프레임을 추출한다.
				case VIDEO:
					
					if (!this._ffmpegFrameExtractor.extractFrame(mediaRealPath, thumbnailRealPath, mediaInfo.getWidth(), mediaInfo.getHeight(), thumbnailWidth, thumbnailTimeSecond, session))
					{
						success = this._mtnFrameExtractor.extractFrame(mediaRealPath, thumbnailRealPath, mediaInfo.getWidth(), mediaInfo.getHeight(), thumbnailWidth, thumbnailTimeSecond, session);
					}
					
					break;
					
				default:
					
					success = false;
					
					break;
			}
			
			if (success)
			{
				File thumbnailFile = new File(thumbnailRealPath);
				
				// 섬네일의 아카이브 정보를 생성한다.
				Archive contentsMediaThumbnailArchive = new Archive();
				
				contentsMediaThumbnailArchive.setContentType("image/jpeg");
				contentsMediaThumbnailArchive.setExtension(FFMpegFrameExtractor._FRAME_IMAGE_EXTENSION);
				contentsMediaThumbnailArchive.setFileSize(thumbnailFile.length());
				contentsMediaThumbnailArchive.setMaskedName(thumbnailMaskedName);
				contentsMediaThumbnailArchive.setOriginalName(thumbnailMaskedName);
				contentsMediaThumbnailArchive.setRelativePath(contentsMediaArchive.getRelativePath());
				contentsMediaThumbnailArchive.setUploadIp(request.getRemoteHost());
			
				// 섬네일의 아카이브 정보를 저장한다.
				if (this._multipartService.saveArchive(contentsMediaThumbnailArchive))
				{
					map.put("contentsMediaThumbnailArchive", contentsMediaThumbnailArchive);
				}
			}
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 미디어 정보 추출 요청에 대한 처리 결과를 뷰로 전달한다.
	 * @param request HTTP 서블릿에 대한 요청 정보가 전달된다.
	 * @param archiveId 미디어 정보를 가져오려는 아카이브의 아이디가 전달된다.
	 * @return 미디어 정보를 포함하는 {@link org.springframework.web.servlet.ModelAndView.ModelAndView} 개체를 반환한다.
	 */
	@RequestMapping(value="/mediainfo", method=RequestMethod.GET)
	public ModelAndView getMediaInfo(HttpServletRequest request,
			@RequestParam(value="archiveId", required=true)Long archiveId)
	{
		Map<String, Object> map = new HashMap<String, Object>();
		
		try
		{
			Archive archive = this._multipartService.getArchive(archiveId);
			
			MediaInfo mediaInfo = this.getMediaInfo(archive);
			
			map.put("mediaInfo", mediaInfo);
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}

		return new ModelAndView("jsonView", map);
	}
	
	/**
	 * 아카이브에 대한 미디어 정보를 추출한다.
	 * @param archive 미디어 정보를 추출하려는 아카이브 정보를 설정한다.
	 * @return 주어진 아카이브에 대한 미디어 정보가 반환된다.
	 */
	private MediaInfo getMediaInfo(Archive archive)
	{
		try
		{
			// Archive 정보 중 ContentsType의 파일 종류 부분을 추출한다.
			String fileType = archive.getContentType().substring(0, archive.getContentType().indexOf("/"));
			
			// 파일의 절대 경로를 조합한다.
			String filePath = String.format("%s%s%s",
					MultipleFileSupportMultipartResolver.getUploadRootDirectory(),
					archive.getRelativePath(),
					archive.getMaskedName());
			
			this._mediaInfoWrapper.open(filePath);
			
			MediaInfo mediaInfo = new MediaInfo();
			
			// 파일 종류에 따라 파일의 세부 정보를 추출한다.
			switch (SupportContentsType.cast(fileType))
			{
				case VIDEO:
					
					mediaInfo.setDuration(Integer.valueOf(_mediaInfoWrapper.get(StreamType.General, 0, "Duration")));
					mediaInfo.setWidth(Integer.valueOf(_mediaInfoWrapper.get(StreamType.Video, 0, "Width")));
					mediaInfo.setHeight(Integer.valueOf(_mediaInfoWrapper.get(StreamType.Video, 0, "Height")));
					
					break;
					
				case IMAGE:
					
					mediaInfo.setWidth(Integer.valueOf(this._mediaInfoWrapper.get(StreamType.Image, 0, "Width")));
					mediaInfo.setHeight(Integer.valueOf(this._mediaInfoWrapper.get(StreamType.Image, 0, "Height")));
					
					break;
					
				case AUDIO:
					
					mediaInfo.setDuration(Integer.valueOf(this._mediaInfoWrapper.get(StreamType.General, 0, "Duration")));
					
					break;
					
				default:
					
					break;
			}
			
			return mediaInfo;
		}
		catch (Exception error)
		{
			logger.error(error.getMessage(), error);
		}
		finally
		{
			this._mediaInfoWrapper.close();
		}
		
		return null;
	}
}